classdef StatisticsManager< Singleton
    %STATISTICSMANAGER Summary of this class goes here
    %   Detailed explanation goes here
    
    properties
        topology = [];
        file_id;
    end
   
    
     methods(Static)
       function obj = instance(should_reinit)
         persistent unique_instance;
         should_reinit = (nargin>0);
         if isempty(unique_instance) || (should_reinit==true)
            unique_instance = StatisticsManager();
         end
         obj = unique_instance;
      end
     end
   
    methods     
        function obj = StatisticsManager()
            params = ParamsSingleton.instance();
            %obj.file_id = fopen(params.statistics_output_file, 'a+');
        end
        
        function set_topology(obj, top)
            obj.topology = top;
        end
        
        function statistics = get_and_print_statistics(obj)
            obj.open_file();
            params = ParamsSingleton.instance();
            statistics = GenericStruct();
           % delivery rate
           packets_manager = PacketsManager.instance();
           delivery_ratio = packets_manager.calc_delivery_rate();
           fprintf(obj.file_id,'============== simulation statistics =====================\n\n');
           clock_vec = clock();
           fprintf(obj.file_id,'time %s:%s:%s\n', num2str(clock_vec(4)),num2str(clock_vec(5)),num2str(clock_vec(6)));
           fprintf(obj.file_id,'link loss rate [precentage]: %d \n',params.link_loss_rate_in_precentage);
           fprintf(obj.file_id,'router %s \n\n',class(obj.topology.router));
           
           fprintf(obj.file_id,'================== delivery ratio =======================\n');
            fprintf(obj.file_id,'delivery ratio %f\n\n',delivery_ratio);
            addprop(statistics,'delivery_rate');
            statistics.delivery_rate = delivery_ratio;
            
            %disjoint paths
            number_of_disjoint_paths = obj.calc_number_of_disjoint_paths();
            fprintf(obj.file_id,'================== routing info =======================\n');
            fprintf(obj.file_id,'number of disjoint paths %f\n\n',number_of_disjoint_paths);
            addprop(statistics,'number_of_disjoint_paths');
            statistics.number_of_disjoint_paths = number_of_disjoint_paths;
            
            %network load
            fprintf(obj.file_id,'================== network load =======================\n');
            avg_load = obj. calc_avg_number_of_bytes_per_link();
            fprintf(obj.file_id,'\n');
            addprop(statistics,'avg_network_load');
            statistics.avg_network_load = avg_load;
            
            %throughput
            fprintf(obj.file_id,'================== network throughput =======================\n');
            throughput = obj.calc_node_raw_throughput_in_kbps();
            [data_received_in_MB avg_node_received_data_in_MB] = obj.calc_node_received_data_in_MB();
            for i=1:length(throughput)
                fprintf(obj.file_id, 'node id %d:  throughput %s [kbps]. data received: %s [MB]\n',throughput(1,i),num2str(throughput(2,i)),num2str(data_received_in_MB(2,i)));
            end
            fprintf(obj.file_id,'avg_node_received_data_in_MB: %d\n',avg_node_received_data_in_MB);
            addprop(statistics,'avg_node_received_data_in_MB');
            statistics.avg_node_received_data_in_MB = avg_node_received_data_in_MB;
            
            %number of packet duplicates
            avg_num_of_duplicates = obj.calc_avg_number_of_packet_duplicates();
            fprintf(obj.file_id,'\n================== ROMER statistics ======================\n');
            fprintf(obj.file_id, 'ROMER alpha: %f \n',params.romer_alpha);
            fprintf(obj.file_id, 'ROMER threshold power: %f \n',params.ROMER_threshold_power);
            fprintf(obj.file_id, 'avg num of duplicates: %f \n',avg_num_of_duplicates);
            fprintf(obj.file_id, 'num of packets dropped because of threshold: %f \n',obj.topology.router.number_of_packets_discarded_because_of_threshold);
            fprintf(obj.file_id,'\n');
                       
            addprop(statistics,'romer_avg_num_of_duplicates');
            statistics.romer_avg_num_of_duplicates = avg_num_of_duplicates;
            
            
            addprop(statistics,'romer_num_of_packets_dropped');
            statistics.romer_num_of_packets_dropped = obj.topology.router.number_of_packets_discarded_because_of_threshold;
            
            obj.close_file();
        end
            
        function throughput_vec = calc_node_raw_throughput_in_kbps(obj)
            ck = ClockSingleton.instance();
            current_time_in_msec = ck.current_time_in_msec;
            nodes = obj.topology.nodes;
            throughput_vec(2,length(nodes)) = 0;
            
            for i=1:length(nodes)
                curr_node = nodes(i);
                throughput_vec(1,i) = curr_node.id;
                throughput_vec(2,i) = ((curr_node.size_of_data_in_bytes_received*8)/(current_time_in_msec/1000))/1024;
            end
        end
        
        function avg_bytes_per_link = calc_avg_number_of_bytes_per_link(obj)
            avg_bytes_per_link = 0;
            num_of_links = 0;
            nodes = obj.topology.nodes;
            for i=1:length(nodes)
                curr_node = nodes(i);
                bytes_per_link = curr_node.node_adapter.number_of_bytes_transferred_per_channel;
                num_of_links = num_of_links+length(bytes_per_link);
                avg_bytes_per_link = avg_bytes_per_link + sum(bytes_per_link);
                fprintf(obj.file_id,'node id %d: links avg number of bytes transferred %d\n',curr_node.id,sum(bytes_per_link)/length(bytes_per_link));
            end
            avg_bytes_per_link = avg_bytes_per_link/num_of_links;
            fprintf(obj.file_id,'avg link num of KB: %s\n',num2str(avg_bytes_per_link/1024));
        end
        
        function [received_data_vec avg_data_size] = calc_node_received_data_in_MB(obj)
            nodes = obj.topology.nodes;
            received_data_vec(2,length(nodes)) = 0;
            avg_data_size = 0;
            
            for i=1:length(nodes)
                curr_node = nodes(i);
                received_data_vec(1,i) = curr_node.id;
                received_data_vec(2,i) = curr_node.size_of_data_in_bytes_received/1024/1024;
                avg_data_size = avg_data_size+received_data_vec(2,i);
            end
            avg_data_size = avg_data_size/length(nodes);
        end
        
        function avg_num= calc_avg_number_of_packet_duplicates(obj)
            avg_num =obj.topology.router.avg_number_of_packet_duplicates();
        end
        
        function print_route(obj, packet_route)
            Utils.instance().trace(sprintf('Packet route: '));
            for j = 1:length(packet_route);
                if j ~= length(packet_route)
                    Utils.instance().trace(sprintf('%d =>',packet_route(j)));
                else
                    Utils.instance().trace(sprintf('%d\r\n\r\n',packet_route(j)));
                end
            end 
        end
        
        function num_of_disjoint_paths = calc_number_of_disjoint_paths(obj)
            packets_manager = PacketsManager.instance();
            delivered_multi_packets = packets_manager.delivered_multi_packets;
            total_routes_matrix = zeros(length(delivered_multi_packets), obj.topology.total_num_of_nodes);
            
            for i=1:length(delivered_multi_packets)
                current_multi_packet = packets_manager.delivered_multi_packets(i);
                currnet_packet = current_multi_packet.packet;
                fprintf(obj.file_id, 'delivered multi packet id: %d. source node: %d.  dest node: %d. route length: %d\n', current_multi_packet.id, currnet_packet.src_node.id, currnet_packet.dest_node.id, length(currnet_packet.route));
                
                packet_route = currnet_packet.route;
                total_routes_matrix(i, 1:length(packet_route)) = packet_route;                
                
                %obj.print_route(packet_route);
            end
            
            different_paths = unique(total_routes_matrix, 'rows');
            num_of_disjoint_paths = size(different_paths, 1);
        end
        
        function open_file(obj)
            params = ParamsSingleton.instance();
            obj.file_id = fopen(params.statistics_output_file, 'a+');
        end
        
        function close_file(obj)
            params = ParamsSingleton.instance();
            fclose(obj.file_id);
        end
    end
    
end

